import 'dart:convert';
import 'dart:io';

class NmScript {
  var matchSys;
  final String path;

  var canUse = false;
  var stdout = []; // `nm framework`的输出
  var cls = []; // framework中所有的类
  /// FOUNDATION_EXPORT NSString * AFPercentEscapedStringFromString(NSString *string);
  var expConst = []; // 常量

  NmScript({this.path}) {
    matchSys = getSystemLibsPrefix();
  }

  Future<NmScript> useFactory() async {
    canUse = await checkEnv();

    if (!canUse) return this;

    stdout = await getDt();

    cls = getAllClass();

    /// 为了兼容一些类只作为参数导致`_OBJC_CLASS_`获取不了的问题
    var fCls = getAllSymbols();

    for (var el in fCls) {
      if (el.contains('+')) continue;
      if (cls.contains(el)) continue;
      cls.add(el);
    }

    expConst = await getAllConst();

    return this;
  }

  /// 检查环境是否存在
  Future<bool> checkEnv() async {
    var process = await Process.start('which', ['nm']);
    if (await process.stdout.isEmpty) {
      return false;
    }

    return true;
  }

  /// 获取 `nm file.framework`的输出
  Future<List> getDt() async {
    var stdoutArr = <String>[];

    var process = await Process.start('nm', [path]);
    await process.stdout.forEach((element) {
      if (element.isNotEmpty) {
        var out = utf8.decode(element);
        stdoutArr.addAll(out.split('\n'));
      }
    });

    return stdoutArr;
  }

  Future<List> getAllUndefineDt() async {
    var stdoutArr = <String>[];

    var process = await Process.start('nm', ['-u', path]);
    await process.stdout.forEach((element) {
      if (element.isNotEmpty) {
        var out = utf8.decode(element);
        var tmpt = out.split('\n');
        for (var i in tmpt) {
          if (i.contains(RegExp('^_(?!$matchSys)+'))) {
            i = i.replaceFirst('_', '');
            stdoutArr.add(i);
          }
        }
      }
    });
    return stdoutArr;
  }

  /// 获取所有类的名字
  List<dynamic> getAllClass() {
    var clsOBJ = ['OBJC_CLASS', 'OBJC_METACLASS', 'OBJC_PROTOCOL'];
    var cls = [];

    clsOBJ.forEach((el) {
      var exp = RegExp('_${el}_\\\$_(?!$matchSys)[A-Za-z0-9+_-]+');
      stdout.forEach((tdout) {
        if (exp.hasMatch(tdout)) {
          var patternStr = exp.firstMatch(tdout).group(0);
          var str = patternStr.replaceAll('_${el}_\$_', '');
          if (!cls.contains(str)) cls.add(str);
        }
      });
    });

    return cls;
  }

  /// 获取到所有`.o`文件。
  List<String> getAllSymbols() {
    var clsArr = <String>[];
    final exp = RegExp(r'\([A-Za-z0-9+_-]+.o\)');

    stdout.forEach((tdout) {
      if (exp.hasMatch(tdout)) {
        var group = exp.firstMatch(tdout).group(0);

        group = group.replaceAll('(', '');
        group = group.replaceAll('.o)', '');

        clsArr.add(group);
      }
    });

    return clsArr;
  }

  /// 获取所有常量
  Future<List<String>> getAllConst() async {
    var clsArr = <String>[];

    var undefine = await getAllUndefineDt();

    /*
    T : FOUNDATION_EXPORT 常量
    S : 常量
    t : static 静态方法 static NSArray * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust){}
    d : static NSString * kSDCGImagePropertyWebPDelayTime = @"DelayTime";
    */
    var constSym = ['T', 'S', 't', 's', 'd'];

    constSym.forEach((el) {
      final exp = RegExp(' $el _(?!$matchSys)([a-zA-Z\\d\\s\\w\\d-\\+]+)');

      for (var tdout in stdout) {
        if (!exp.hasMatch(tdout)) continue;

        var group = exp.firstMatch(tdout).group(0);

        var check = group.replaceAll('$el ', '');
        if (undefine.contains(check)) continue;

        group = group.replaceAll(' $el _', '');
        if (clsArr.contains(group)) continue;

        // 对比符号变量在正则前后是否一致。
        var comp = (tdout as String).split(' $el _').last;

        if (group == comp) {
          clsArr.add(group);
        }
      }
    });
    return clsArr;
  }

  String getSystemLibsPrefix() {
    var p = [
      'AR',
      'AV',
      'BL',
      'AX',
      'AC',
      'AA',
      'AS',
      'AB',
      'AP',
      'NS',
      'AT',
      'AL',
      'AU',
      'CA',
      'AE',
      'BG',
      'BC',
      'CF',
      'CX',
      'CP',
      'CL',
      'CK',
      'CN',
      'UI',
      'CB',
      'CG',
      'CH',
      'CI',
      'MI',
      'ML',
      'CM',
      'NF',
      'UT',
      'CS',
      'CT',
      'SF',
      'CV',
      'TK',
      'DC',
      'EK',
      'EN',
      'EA',
      'FP',
      'GL',
      'GS',
      'GC',
      'GK',
      'HK',
      'HM',
      'IO',
      'IL',
      'IC',
      'IN',
      'JS',
      'LP',
      'LA',
      'MK',
      'MA',
      'MP',
      'MS',
      'MT',
      'MF',
      'MX',
      'MD',
      'MC',
      'NL',
      'NI',
      'NE',
      'NW',
      'NK',
      'NC',
      'OS',
      'PD',
      'PK',
      'PH',
      'QL',
      'RP',
      'SS',
      'SC',
      'ST',
      'SR',
      'SL',
      'SN',
      'SK',
      'DH',
      'TW',
      'UN',
      'VS',
      'VT',
      'VN',
      'WC',
      'WK',
      'WG',
      'AD',
      'OBJC',
      'kAR',
      'kAV',
      'kBL',
      'kAX',
      'kAC',
      'kAA',
      'kAS',
      'kAB',
      'kAP',
      'kNS',
      'kAT',
      'kAL',
      'kAU',
      'kCA',
      'kAE',
      'kBG',
      'kBC',
      'kCF',
      'kCX',
      'kCP',
      'kCL',
      'kCK',
      'kCN',
      'kUI',
      'kCB',
      'kCG',
      'kCH',
      'kCI',
      'kMI',
      'kML',
      'kCM',
      'kNF',
      'kUT',
      'kCS',
      'kCT',
      'kSF',
      'kCV',
      'kTK',
      'kDC',
      'kEK',
      'kEN',
      'kEA',
      'kFP',
      'kGL',
      'kGS',
      'kGC',
      'kGK',
      'kHK',
      'kHM',
      'kIO',
      'kIL',
      'kIC',
      'kIN',
      'kJS',
      'kLP',
      'kLA',
      'kMK',
      'kMA',
      'kMP',
      'kMS',
      'kMT',
      'kMF',
      'kMX',
      'kMD',
      'kMC',
      'kNL',
      'kNI',
      'kNE',
      'kNW',
      'kNK',
      'kNC',
      'kOS',
      'kPD',
      'kPK',
      'kPH',
      'kQL',
      'kRP',
      'kSS',
      'kSC',
      'kST',
      'kSR',
      'kSL',
      'kSN',
      'kSK',
      'kDH',
      'kTW',
      'kUN',
      'kVS',
      'kVT',
      'kVN',
      'kWC',
      'kWK',
      'kWG',
      'kAD',
      'kOBJC',
    ];
    return p.join('|');
  }
}
